package com.example.portablefortheelderlybackground.sevice.Impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.portablefortheelderlybackground.common.Message.ResultMessage;
import com.example.portablefortheelderlybackground.common.StringCommon;
import com.example.portablefortheelderlybackground.common.User.loadUserDetails;
import com.example.portablefortheelderlybackground.common.redis.redisKey;
import com.example.portablefortheelderlybackground.config.Exception.user.TokenInvalidationException;
import com.example.portablefortheelderlybackground.config.Security.RequestValidationHeader;
import com.example.portablefortheelderlybackground.pojo.user;
import com.example.portablefortheelderlybackground.pojo.userDetailImpl;
import com.example.portablefortheelderlybackground.sevice.menuService;
import com.example.portablefortheelderlybackground.sevice.roleMenuService;
import com.example.portablefortheelderlybackground.utils.*;
import com.example.portablefortheelderlybackground.mapper.userMapper;
import com.example.portablefortheelderlybackground.sevice.userRoleService;
import com.example.portablefortheelderlybackground.sevice.userService;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.example.portablefortheelderlybackground.Enum.Message.ReturnMessageEnum.*;
import static com.example.portablefortheelderlybackground.utils.JWTUtil.CLAIM_KEY_USERDETAILS;

@Service
@Slf4j
@Transactional(timeout = 3)//设置超时时间
public class userServiceImpl extends ServiceImpl<userMapper, user> implements userService {

    //authenticationManager调用authenticate进行认证
    private final AuthenticationManager authenticationManager;

    private final RedisTemplate<String, Object> redisTemplate;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final JWTUtil jwtUtil;
    private final userRoleService userRoleService;
    private final menuService menuService;
    private final roleMenuService roleMenuService;
    private final RequestValidationHeader validationHeader;

    //选择构造器注入
    @Autowired
    public userServiceImpl(AuthenticationManager authenticationManager, RedisTemplate<String, Object> redisTemplate, BCryptPasswordEncoder bCryptPasswordEncoder, JWTUtil jwtUtil, userRoleService userRoleService, com.example.portablefortheelderlybackground.sevice.menuService menuService, com.example.portablefortheelderlybackground.sevice.roleMenuService roleMenuService, RequestValidationHeader validationHeader) {
        this.authenticationManager = authenticationManager;
        this.redisTemplate = redisTemplate;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
        this.jwtUtil = jwtUtil;
        this.userRoleService = userRoleService;
        this.menuService = menuService;
        this.roleMenuService = roleMenuService;
        this.validationHeader = validationHeader;
    }


    @Override
    public Response<String> updateUserInfo(user user) {
        //根据id修改
        int i = baseMapper.updateById(user);
        if (i == 1) {
            return Response.success(ResultMessage.success_message);
        }
        log.info("服务繁忙,请稍后再试");
        return Response.error(ResultMessage.error_message);
    }

    @Override
    public Response<Map<String, Object>> loginIn(String name, String password) {
        if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
            return Response.error(null, ResultMessage.setSelfMessage("用户名或密码不能为空"));
        }
        //进行认证
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(name, password);
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if (ObjectUtils.isEmpty(authenticate)) {//如果authenticate为空
            return Response.error(null, ResultMessage.setSelfMessage("认证未通过"));
        }
        //认证通过
        userDetailImpl principal = (userDetailImpl) authenticate.getPrincipal();//Principal封装的是userDetail,查看源码
        Map<String, Object> map = loadUserDetails.returnUserInfoAndCache(jwtUtil, principal, redisTemplate, validationHeader);
        return Response.success(map, ResultMessage.setSelfMessage("登录成功!"));
    }

    @Override
    public Response<user> signIn(user userEntity, String code) {
        log.info("${}的验证码${}", userEntity.getName(), code);
        if (ObjectUtils.isEmpty(userEntity) && StringUtils.hasLength(userEntity.getPhone())) {
            return Response.error(ResultMessage.setSelfMessage("请填写用户信息(手机号必填),并注册"));
        }
        //先判断用户是否采用验证码登录
        if (StringUtils.hasLength(code)) {
            String phoneCode = (String) redisTemplate.opsForValue().get(userEntity.getPhone());
            //判断验证码是否相同
            if (!VerifyCodeUtil.VerifyCodeEquals(phoneCode, code)) {
                return Response.error(ResultMessage.setSelfMessage("验证码错误"));
            }
        }

        LambdaQueryWrapper<user> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StringUtils.hasLength(userEntity.getName()), user::getName, userEntity.getName());
        user isLoginIn = baseMapper.selectOne(queryWrapper);
        if (!ObjectUtils.isEmpty(isLoginIn)) {
            return Response.error(null, ResultMessage.setSelfMessage("用户已存在!"));
        }
        //在插入之前对密码进行盐加密
        userEntity.setPassword(bCryptPasswordEncoder.encode(userEntity.getPassword()));
        int insertFlag = baseMapper.insert(userEntity);
        if (insertFlag != 0) {
            log.info("id为{}成功注册-{}", userEntity.getId(), LocalDateTime.now());
            //对新注册用户添加默认的权限
            boolean saveFlag = userRoleService.save(userRoleUtils.getUserRole(userEntity.getId(), StringCommon.NormalUserRoleId));
            if (!saveFlag) {
                //如果添加失败,删除对应userEntity
                baseMapper.deleteById(userEntity);
                return Response.error(ResultMessage.error_message);
            }
            return Response.success(userEntity, ResultMessage.success_message);
        }
        log.error("用户注册失败-{}", LocalDateTime.now());
        return Response.error("服务繁忙,请稍后再试!");
    }

    @Override
    public boolean sendMessageByPhone(String... phone) {
        //判断验证码是否存在,存在则返回false
        if (!ObjectUtils.isEmpty(redisTemplate.opsForValue().get(phone[0]))) {
            return false;
        }
        String phoneCode = VerifyCodeUtil.generatePhoneCode();
        log.info("${}的验证码为${}", phone, phoneCode);
        //将验证码存入redis缓存
        redisTemplate.opsForValue().set(phone[0], phoneCode, StringCommon.SMSCodeTimeOut, TimeUnit.MINUTES);
        return TencentSmsUtil.sendMessage(phoneCode, StringCommon.PREFIX_CHINA_PHONE + phone[0]);
    }

    @Override
    public Response<Map<String, Object>> loginByPhoneCode(String phone, String code) {
        //先检验数据库是否存在该用户,未绑定手机号也视作不存在
        LambdaQueryWrapper<user> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(user::getPhone, phone);
        user user = baseMapper.selectOne(queryWrapper);
        if (ObjectUtils.isEmpty(user)) {
            //不存在该用户返回错误信息
            return Response.status(null, PHONE_CODE_ERROR.getCode(), PHONE_CODE_ERROR.getMessage());
        }
        //与redis的缓存进行匹配
        String PhoneVerifyCode = (String) redisTemplate.opsForValue().get(phone);
        //校验密码是否相等
        if (!VerifyCodeUtil.VerifyCodeEquals(code, PhoneVerifyCode)) {
            return Response.status(null, PHONE_CODE_ERROR.getCode(), PHONE_CODE_ERROR.getMessage());
        }
        //验证码通过
        userDetailImpl userDetails = (userDetailImpl) loadUserDetails.getUserDetails(userRoleService, roleMenuService, menuService, user);
        //需要生成 token
        //创建map,存放token和设置缓存
        Map<String, Object> map = loadUserDetails.returnUserInfoAndCache(jwtUtil, userDetails, redisTemplate, validationHeader);
        //返回用户信息和token
        return Response.status(map, SUCCESS.getCode(), SUCCESS.getMessage());
    }

    @Override
    public Response<Map<String, Object>> userRefreshToken(String refreshToken) {
        //如果用户能进入,则表示用户的token是没有过期的,因为在jwt过滤器已经进行了判断
        //调用jwtUtils重新生成token
        String newToken = jwtUtil.refreshToken(refreshToken);
        //如果newToken生成失败
        if (!StringUtils.hasText(newToken)) {
            throw new TokenInvalidationException(TOKEN_REFRESHFAIL.getMessage());
        }
        //如果newToken生成成功
        //获取当前的用户名,更新redis的用户缓存,并将token返回给用户重新保存
        String usernameFromToken = jwtUtil.getUsernameFromToken(newToken);
        if (!StringUtils.hasText(usernameFromToken)) {
            throw new TokenInvalidationException(TOKEN_REFRESHFAIL.getMessage());
        }
        //解析newToken解析获取userDetails
        Claims claimsFromToken = jwtUtil.getClaimsFromToken(newToken);
        //利用jsonUtil将从claim获取到map序列化为json,再将得到的json重新序列化为userDetailImpl对象
        userDetailImpl userDetail = JSONUtil.toBean(JSONUtil.toJsonStr(claimsFromToken.get(CLAIM_KEY_USERDETAILS)), userDetailImpl.class);
        if (ObjectUtils.isEmpty(userDetail)) {
            throw new TokenInvalidationException(TOKEN_REFRESHFAIL.getMessage());
        }
        //根据用户名更新redis缓存
        redisTemplate.opsForValue().set(redisKey.usernameId + usernameFromToken, userDetail, StringCommon.TimeOut, TimeUnit.HOURS);
        //将新的token返回给用户
        //创建map存放token和用户实体
        Map<String, Object> map = new HashMap<>();
        map.put(validationHeader.Authentication, newToken);
        //将refreshToken一并返回
        map.put(validationHeader.refreshAuthentication, refreshToken);
        map.put("user", userDetail.getUser());
        return Response.success(map, TOKEN_REFRESHSUCCESS.getCode(), TOKEN_REFRESHSUCCESS.getMessage());
    }
}
